test

9.4 自定义事件

JRockit提供的方便的Java API来向JFR添加自定义事件。相关接口在com.oracle.jrockit.jfr包下,位于rt.jar中,随JRockit JDK一起发行。

目前com.oracle.jrockit.jfr包下的API还在开发中,还不能支持已经发型的Oracle JRockit。某些内部产品(例如WebLogic Server)已经在JFR中使用该API了。

To create a custom event using the API, first decide what kind of event is needed. You may recall from the start of this chapter that there are four main event types. Depending on what kind of event is needed, a different event class will need to be extended. There are four different ones, each corresponding to a different kind of event:

若要添加自定义事件,首先要确定事件的类型。在本章的开始部分曾经介绍过共有4中主要的事件类型。依据具体的需求,需要扩展不同的事件类:

  • com.oracle.jrockit.jfr.InstantEvent
  • com.oracle.jrockit.jfr.DurationEvent
  • com.oracle.jrockit.jfr.TimedEvent
  • com.oracle.jrockit.jfr.RequestableEvent

事件也可以动态创建,这部分内容将在后面介绍。

在下面示例中,会创建一个简单的事件,当调用日志服务时进行记录。按照需求,需要知道调用日志服务的持续时间,因此选择计时事件类型。此外,还需要设置一个阈值,只有当持续时间超过该阈值时,才会进行记录。

创建事件的代码很简单,如下所示:

import com.oracle.jrockit.jfr.EventDefinition;
import com.oracle.jrockit.jfr.TimedEvent;
import com.oracle.jrockit.jfr.ValueDefinition;

@EventDefinition(name = "logentry")
public class LogEvent extends TimedEvent {
    @ValueDefinition(name = "message")
    private String text;
    public LogEvent(String text) {
        this.text = text;
    }
    public String getText() {
        return text;
    }
}

在Java应用程序中使用时,只需创建一个事件实例,以下面的形式来调用:

public synchronized void log(String text) {
    LogEvent event = new LogEvent(text);
    event.begin();
    // Do logging here
    event.end();
    event.commit();
}

但在使用事件之前,还需要创建并注册一个事件生产者:

private static Producer registerProducer() {
    try {
        Producer p;
        p = new Producer("Log Producer (Demo)",
            "A demo event producer for the demo logger.",
            "http://www.example.com/logdemo");
        p.addEvent(LogEvent.class);
        p.register();
        return p;
    } catch (Exception e) {
        // Add proper exception handling.
        e.printStackTrace();
    }
    return null;
}

registerProducer方法返回的Producer实例在事件记录过程中都需要存在。

就这些,大功告成。但是,上面的代码执行效率很差。每次创建事件市里的时候,都会隐式的查找相关联的事件类型。实际上,如果在创建Producer实例的时候能够传入addEvent()方法返回的事件符号(EventToken),就可以避免重复进行全局查找了。

此外,如果期望记录引擎能够提供调用栈信息和线程信息,则可以将代码修改为下面的样子:

import com.oracle.jrockit.jfr.EventDefinition;
import com.oracle.jrockit.jfr.EventToken;
import com.oracle.jrockit.jfr.TimedEvent;
import com.oracle.jrockit.jfr.ValueDefinition;

@EventDefinition(path = "log/logentry", name = "Log Entry",
    description = "A log call in the custom logger.",
    stacktrace = true, thread = true)
public class LogEvent extends TimedEvent {
    @ValueDefinition(name = "Message", description = "The logged message.")
    private String text;
    public LogEvent(EventToken eventToken, String text) {
        super(eventToken);
        this.text = text;
    }
    public String getText() {
        return text;
    }
}

因此,在注册事件生产者时,需要将引用保存下来:

static EventToken token;
static Producer producer;

static {
    registerProducer();
}

static void registerProducer() {
    try {
        producer = new Producer("Log Producer (Demo)",
            "A demo event producer for the demo logger.",
            "http://www.example.com/logdemo");
        token = producer.addEvent(LogEvent.class);
        producer.register();
    } catch (Exception e) {
        // Add proper exception handling.
        e.printStackTrace();
    }
}

事件符号的使用方式如下所示:

public synchronized void log(String text) {
    LogEvent event = new LogEvent(token, text);
    event.begin();
    // Do logging here
    event.end();
    event.commit();
}

此外,如果能保证事件的应用程序场景是线程安全的,则可以将text属性置为可写的,对事件实例的存储和重用可以改为如下形式:

private LogEvent event = new LogEvent(token);

public synchronized void log(String text) {
    event.reset();//clear the instance for reuse
    event.setText(text);
    event.begin();
    // Do logging here
    event.end();
    event.commit();
}

默认情况下,事件是禁用的。若要启动事件记录,需要在使用的模板中将之开启。此外,还可以通过编程的方式来启用,创建一个带有已启用事件的记录即可。下面的代码展示了如何创建记录,并在其中启用事件:

FlightRecorderClient fr = new FlightRecorderClient();
FlightRecordingClient rec = fr.createRecordingObject("tmp");

for (CompositeData pd : fr.getProducers()) {
    if (!PRODUCER_URI.equals(pd.get("uri"))) {
        continue;
    }
    CompositeData events[] = (CompositeData[]) pd.get("events");
    for (CompositeData d : events) {
        int id = (Integer) d.get("id");
        rec.setEventEnabled(id, true);
        rec.setStackTraceEnabled(id, true);
        rec.setThreshold(id, 200);
        rec.setPeriod(id, 5);
        System.out.println("Enabled event " + d.get("name"));
    }
}
rec.close();